import requests
from bs4 import BeautifulSoup
import re
import os
import time
import pymysql
from pathlib import Path
import wave
import io
from concurrent.futures import ThreadPoolExecutor
from functools import partial
import json

class BiQuGeAPI:
    def __init__(self):
        self.base_url = "http://www.ibiquw.info"
        self.headers = {
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
        }
        #书的音频 http://192.168.187.166:9880
        #http://110.40.39.137:22114
        #http://direct.virtaicloud.com:28143/
        self.audio_url = "http://110.40.39.137:22114/"
        self.audio_dir = r"C:\workspace\xiaozhi\xiaozhi-esp32-server-1.0\main\xiaozhi-server\book_audio"
        self.payload = {
            #参考音频，地址对应部署的服务器上，长度在3-10s之间
            "refer_wav_path": "/data1/github/GPT-SoVITS/wav/bl.wav",
            #"refer_wav_path": "/gemini/code/template/ly.wav",
            #提示词和参考音频内容一样
            #"prompt_text": "你拨打的用户在5秒钟之前已经将手机摔到588米开外了，要想让机主接通电话。",
            #"prompt_text": "床前明月光，疑是地上霜。举头望明月，低头思故乡。",
            "prompt_text":"早上好呀，起床啦，又是新的一天，不管今天是晴天还是雨天，你都会有阳光明媚的一天，因为有我和你在一起呦！",
            "prompt_language": "zh",
            "text_language": "zh",
            "top_k": 15,
            "top_p": 1.0,
            "temperature": 1.0,
            "cut_punc": "",
            "speed": 1.0,
            "inp_refs": [],
            "sample_steps": 32,
            "if_sr": "false"
        }


        # 数据库配置
        self.db_config = {
            "host": "localhost",
            "port": 3308,
            "user": "root",
            "password": "123456",
            "database": "test",
            "charset": "utf8mb4",  # 支持存储特殊字符和emoji
        }

        self.executor = ThreadPoolExecutor(max_workers=10)  # 设置线程数，根据你机器性能调整

    def _get_db_connection(self):
        """建立数据库连接"""
        try:
            return pymysql.connect(**self.db_config)
        except pymysql.Error as err:
            print(f"数据库连接失败: {err}")
            return None

    def get_html(self, search_key):
        """请求搜索接口并返回HTML内容"""
        url = f"{self.base_url}/modules/article/search.php?searchkey={search_key}"
        try:
            response = requests.get(url, headers=self.headers)
            response.raise_for_status()  # 检查HTTP状态码
            return response.text
        except requests.exceptions.RequestException as e:
            print(f"请求失败: {e}")
            return None

    def parse_book_id(self, html):
        """从HTML中解析第一个书籍ID"""
        if not html:
            return None

        soup = BeautifulSoup(html, 'html.parser')
        first_li = soup.find('li')
        if not first_li:
            return None

        first_a = first_li.find('a')
        if not first_a:
            return None

        href = first_a.get('href')
        match = re.search(r'book/(\d+)/', href)
        return match.group(1) if match else None

    def search_book(self, keyword):
        """搜索书籍并返回第一个匹配的ID"""
        html = self.get_html(keyword)
        if html:
            return self.parse_book_id(html)
        return None

    #小说下载成文件
    def download_book(self, book_id):
        # 创建保存小说的文件夹
        if not os.path.exists('./小说'):
            os.mkdir('./小说')
        # 获取小说目录页           http://www.ibiquw.info/book/2332/
        response = requests.get(self.base_url+'/book/'+book_id+'/', headers=self.headers)
        response.encoding = response.apparent_encoding
        soup = BeautifulSoup(response.text, 'lxml')
        # 提取所有章节链接
        book_list = soup.find('div', class_='book_list').find_all('a')

        for book in book_list:
            book_name = book.text + '.txt'
            book_url = self.base_url+'/book/'+book_id+'/' + book['href']

            # 获取章节内容
            book_info_html = requests.get(book_url, headers=self.headers)
            book_info_html.encoding = book_info_html.apparent_encoding
            soup = BeautifulSoup(book_info_html.text, 'lxml')
            info = soup.find('div', id='htmlContent').text

            # 保存章节
            with open('./小说/' + book_name, 'w', encoding='utf-8') as f:
                f.write(info)

            print(book_name, '下载成功！')
            time.sleep(1)  # 防止请求过快

    def fetch_and_display(self, book_id):
        # 获取小说目录页
        response = requests.get(self.base_url + '/book/' + book_id + '/', headers=self.headers)
        response.encoding = response.apparent_encoding
        soup = BeautifulSoup(response.text, 'lxml')

        # 提取所有章节链接
        book_list = soup.find('div', class_='book_list').find_all('a')

        is_start_flag = False

        for book in book_list:

            #如果是true结束整个循环
            if is_start_flag:
                break

            title = book.text  # 移除.txt后缀
            book_url = self.base_url + '/book/' + book_id + '/' + book['href']

            # 获取章节内容
            book_info_html = requests.get(book_url, headers=self.headers)
            book_info_html.encoding = book_info_html.apparent_encoding
            soup = BeautifulSoup(book_info_html.text, 'lxml')
            info = soup.find('div', id='htmlContent').text

            fiter_flag = self.filter_title(title)
            if not fiter_flag:
                continue

            is_start_flag = True;
            #获得正文第一章
            # 找到第一个符合条件的章节后直接返回 (标题 + 正文)
            return title, info
        return None, None  # 没有符合条件的章节


    #存储数据库整本书
    def save_chapter_to_db(self, book_id,book_name,platform):
        conn = self._get_db_connection()
        if not conn:
            return

        try:
            # 获取小说目录页
            response = requests.get(f"{self.base_url}/book/{book_id}/", headers=self.headers)
            response.encoding = response.apparent_encoding
            soup = BeautifulSoup(response.text, 'lxml')
            book_list = soup.find('div', class_='book_list').find_all('a')

            for book in book_list:
                title = book.text.strip()
                book_url = f"{self.base_url}/book/{book_id}/{book['href']}"

                # 获取章节内容
                book_info_html = requests.get(book_url, headers=self.headers)
                book_info_html.encoding = book_info_html.apparent_encoding
                soup = BeautifulSoup(book_info_html.text, 'lxml')
                content = soup.find('div', id='htmlContent').text.strip()

                fiter_flag = self.filter_title(title)
                if not fiter_flag:
                    continue

                # 插入数据库（使用事务确保原子性）
                with conn.cursor() as cursor:
                    insert_sql = """
                    INSERT INTO novel_chapters (title, content,name, platform, book_id)
                    VALUES (%s,%s,%s,%s,%s)
                    ON DUPLICATE KEY UPDATE content = VALUES(content)  # 如果章节已存在则更新内容
                    """
                    try:
                        cursor.execute(insert_sql, (title, content,book_name,platform,book_id))
                        conn.commit()
                        #print(f"章节 '{title}' 已存入数据库")
                    except pymysql.Error as err:
                        #print(f"插入章节 '{title}' 失败: {err}")
                        conn.rollback()

                time.sleep(1)  # 防止请求过快

        except Exception as e:
            print(f"程序运行异常: {e}")
        finally:
            if conn:
                conn.close()

    #同步
    def fetch_and_save_to_mysql(self, book_id,book_name,platform):
        self.save_chapter_to_db(book_id,book_name,platform);

    #异步
    def fetch_and_save_to_mysql_async(self, book_id, book_name, platform):
        """异步入库：主函数调用这个"""
        print(f"📥 异步启动入库任务（{book_name}）")
        self.executor.submit(self.save_chapter_to_db, book_id, book_name, platform)

    # 新增方法：更新音频合成标记
    def update_audio_status(self, title,book_name, synthetic_audio_url,status='1'):
        conn = self._get_db_connection()
        if not conn:
            return False

        try:
            with conn.cursor() as cursor:
                update_sql = """
                UPDATE novel_chapters 
                SET is_synthetic_audio = %s,synthetic_audio_url = %s
                WHERE title = %s AND name = %s
                """
                cursor.execute(update_sql, (status,json.dumps(synthetic_audio_url, ensure_ascii=False) ,title,book_name))
                conn.commit()
                return True
        except pymysql.Error as err:
            print(f"更新音频状态失败: {err}")
            conn.rollback()
            return False
        finally:
            if conn:
                conn.close()




    def search_book_database(self, keyword,number):
        """根据书名查询所有章节，返回 List[Dict]，未找到返回 None"""
        conn = self._get_db_connection()
        if not conn:
            return None

        try:
            with conn.cursor(pymysql.cursors.DictCursor) as cursor:
                sql = """
                    SELECT title, content, platform, book_id ,is_synthetic_audio
                    FROM novel_chapters 
                    WHERE name LIKE  %s 
                    ORDER BY id ASC
                    LIMIT %s
                """
                cursor.execute(sql, (keyword,number))
                results = cursor.fetchall()
                if results:
                    print(f"数据库中已找到《{keyword}》，共 {len(results)} 章")
                    return results  # 返回 List[Dict]
                else:
                    print(f"数据库中未找到《{keyword}》")
                    return None
        except pymysql.MySQLError as e:
            print(f"数据库查询失败: {e}")
            return None
        finally:
            conn.close()


    #查询未合成完的章节
    def search_not_audio_book_database(self, keyword,is_synthetic_audio,number):
        """根据书名查询所有章节，返回 List[Dict]，未找到返回 None"""
        conn = self._get_db_connection()
        if not conn:
            return None

        try:
            with conn.cursor(pymysql.cursors.DictCursor) as cursor:
                sql = """
                    SELECT title, content, platform, book_id ,is_synthetic_audio
                    FROM novel_chapters 
                    WHERE name LIKE  %s  AND is_synthetic_audio = %s
                    ORDER BY id ASC
                    LIMIT %s
                """
                cursor.execute(sql, (keyword,is_synthetic_audio,number))
                results = cursor.fetchall()
                if results:
                    print(f"数据库中已找到《{keyword}》，共 {len(results)} 章")
                    return results  # 返回 List[Dict]
                else:
                    print(f"数据库中未找到《{keyword}》")
                    return None
        except pymysql.MySQLError as e:
            print(f"数据库查询失败: {e}")
            return None
        finally:
            conn.close()
    #查询传入章节的下一章
    def search_next_audio_book_database(self, book_name,title):
        """根据书名查询所有章节，返回 List[Dict]，未找到返回 None"""
        conn = self._get_db_connection()
        if not conn:
            return None

        try:
            with conn.cursor(pymysql.cursors.DictCursor) as cursor:
                sql = """
                    SELECT title, content, platform, book_id ,is_synthetic_audio,synthetic_audio_url
                    FROM novel_chapters 
                    WHERE name = %s AND title != %s
                    ORDER BY id ASC
                    LIMIT 1
                """
                cursor.execute(sql, (book_name,title))
                results = cursor.fetchall()
                if results:
                    return results  # 返回 List[Dict]
                else:
                    return None
        except pymysql.MySQLError as e:
            print(f"数据库查询失败: {e}")
            return None
        finally:
            conn.close()


    #合成音频
    def synthetic_audio(self, text, filename):
        self.payload["text"] = text
        get_book_name = filename.split("\\")[0]
        # 确保保存目录存在
        os.makedirs(self.audio_dir+"\\"+get_book_name, exist_ok=True)
        save_path = os.path.join(self.audio_dir, filename)

        try:
            response = requests.post(self.audio_url, json=self.payload, stream=True)
            response.raise_for_status()

            if response.status_code == 200 and response.headers.get("Content-Type") == "audio/wav":
                with open(save_path, "wb") as f:
                    for chunk in response.iter_content(chunk_size=8192):
                        if chunk:
                            f.write(chunk)
                print(f"音频已保存: {save_path}")
                return save_path
            else:
                print(f"请求失败，状态码: {response.status_code}，Content-Type: {response.headers.get('Content-Type')}")
                return None

        except requests.exceptions.RequestException as e:
            print(f"请求异常: {e}")
        except Exception as e:
            print(f"保存音频出错: {e}")
        return None

    def split_content_by_length(self,text, max_length=100):
        """将内容按每 max_length 字符切分为多段"""
        return [text[i:i + max_length] for i in range(0, len(text), max_length)]

    #过滤下章节只要序章和正文
    def filter_title(self, title):
        # 匹配"序章"、"第xx章"、"第x节"或"第x集"（支持中文数字或阿拉伯数字）
        pattern = r"(序章|第[\d一二三四五六七八九十百千]+[章节集])"
        return bool(re.search(pattern, title))

    def get_text_segments_length(self,origin_segments,book_name,title):
        origin_length: int = len(origin_segments)
        # 循环得到该章节的数组从1开始
        synthetic_audio_url = []
        for i in range(1, origin_length + 1):  # 生成1到10的序列
            filename = book_name + "\\" + f"{title}-第{i}段.wav"
            synthetic_audio_url.append(self.audio_dir + "\\" + filename)
        return synthetic_audio_url


    def async_synthesize(self, segments,origin_segments,title,book_name, start_index=1):
        """异步合成多个段落音频"""
        synthetic_audio_url = self.get_text_segments_length(origin_segments,book_name,title)

        for idx, segment in enumerate(segments, start=start_index):
            filename = book_name+"\\"+f"{title}-第{idx}段.wav"
            self.synthetic_audio(segment, filename)
        #更新合成完成章节的数据库字段
        self.update_audio_status(title,book_name,synthetic_audio_url,'1')

    #具体合成音频并且进行切割
    def cutting_and_synthesis_audio(self,title,content,book_name):
        segments = self.split_content_by_length(content)
        taotal = len(segments)
        print(f"总共分了 {taotal} 段")

        audio_top_arr = []

        # 前5段同步合成
        front_segments = segments[:2]
        for idx, segment in enumerate(front_segments):
            filename = book_name+"\\"+f"{title}-第{idx + 1}段.wav"
            audio_top_arr.append(self.audio_dir+"\\"+filename)
            self.synthetic_audio(segment, filename)

        # 其余异步合成
        if len(segments) > 2:
            rest_segments = segments[2:]
            #第一章后续合成
            future = self.executor.submit(self.async_synthesize, rest_segments,segments,title, book_name,3)
            #剩余的章节
            #future.add_done_callback(partial(self.after_synthesis_audio, book_name))
            future.add_done_callback(lambda f: self.after_synthesis_audio(book_name))
        return audio_top_arr,taotal

    def after_synthesis_audio(self, book_name):
        print(f"{book_name} 开始合并剩余章节")
        # 查数据库
        book_data = self.search_not_audio_book_database(book_name, "0",10000)

        # 如果没数据了，就终止递归
        if not book_data or len(book_data) == 0:
            print(f"{book_name} 所有章节已完成合成")
            return


        for chapter in book_data:
            title = chapter['title']
            content = chapter['content']
            segments = self.split_content_by_length(content)

            #章节合成循环完成后，这章就证明所有音频都合成完毕
            for idx, segment in enumerate(segments):
                filename = book_name + "\\" + f"{title}-第{idx + 1}段.wav"
                self.synthetic_audio(segment, filename)

            synthetic_audio_url = self.get_text_segments_length(segments, book_name, title)

            # 更新合成完成章节的数据库字段
            self.update_audio_status(title, book_name,synthetic_audio_url,'1')

        # 合成完成后，递归调用
        print(f"{book_name} 当前批次章节合成完毕，准备处理下一批...")
        self.after_synthesis_audio(book_name)

    def check_file_exists(self,file_path):
        return os.path.exists(file_path)

    def mainTest(self,book_name):
        #先去拿书名确认下书是否存在数据库中
        book_data  = self.search_book_database("%"+book_name+"%",10000)
        if book_data == None:
            book_id = self.search_book(book_name)
            if book_id:
                # 就是先存储
                self.fetch_and_save_to_mysql_async(book_id, book_name, '笔趣阁')
                # 在请求一次接口进行将第一章的内容提出来
                title, content = self.fetch_and_display(book_id)
                if title and content:
                    audio_top_arr = self.cutting_and_synthesis_audio(title,content,book_name)
                    # 返回先合成的那5个音频
                    print("同步部分合成完毕",audio_top_arr)
                    #交给小智插件进行播放
                else:
                    print("未找到符合条件的章节")
            else:
                print("未找到匹配的书籍ID")
        else:
            print("已经存在了直接找对应的音频就行")
            # 从头放第一章
            #先检查有没有音频，没有就调用逻辑进行合成，先前5个然后在合成后续的进行异步
            for chapter in book_data:
                title = chapter['title']
                content = chapter['content']
                is_synthetic_audio = chapter['is_synthetic_audio']

                segments = self.split_content_by_length(content)

                #file_start = self.audio_dir+"\\"+book_name + "\\" + f"{title}-第1段.wav"
                #file_end = self.audio_dir + "\\" + book_name + "\\" + f"{title}-第{len(segments)}段.wav"

                #如果第一段和最后一段都存在则直接取值 作废
                audio_arr = []
                #if Path(file_start).exists() or Path(file_end).exists():
                #已经合成完直接去取值
                if is_synthetic_audio == '1':
                    for idx, segment in enumerate(segments):
                        file_exits = self.audio_dir+"\\"+book_name + "\\" + f"{title}-第{idx + 1}段.wav"
                        audio_arr.append(file_exits)
                    return audio_arr
                else:
                    #合成该章节，先合前5段 然后在接着合成
                    audio_arr = self.cutting_and_synthesis_audio(title, content, book_name)
                    # 返回先合成的那5个音频
                    print("同步部分合成完毕", audio_arr)
                    return audio_arr

    def main(book_name):
        api = BiQuGeAPI()
        #先去拿书名确认下书是否存在数据库中
        book_data  = api.search_book_database("%"+book_name+"%",10000)
        if book_data == None:
            book_id = api.search_book(book_name)
            if book_id:
                # 就是先存储
                api.fetch_and_save_to_mysql_async(book_id, book_name, '笔趣阁')
                # 在请求一次接口进行将第一章的内容提出来
                title, content = api.fetch_and_display(book_id)
                if title and content:
                    audio_top_arr,total = api.cutting_and_synthesis_audio(title,content,book_name)
                    # 返回先合成的那2个音频
                    print("同步部分合成完毕",audio_top_arr,total)
                    # 交给小智插件进行播放
                    return audio_top_arr,total
                else:
                    print("未找到符合条件的章节")
            else:
                print("未找到匹配的书籍ID")
        else:
            print("已经存在了直接找对应的音频就行")
            # 从头放第一章
            #先检查有没有音频，没有就调用逻辑进行合成，先前5个然后在合成后续的进行异步
            for chapter in book_data:
                title = chapter['title']
                content = chapter['content']
                is_synthetic_audio = chapter['is_synthetic_audio']
                synthetic_audio_url = chapter['synthetic_audio_url']

                segments = api.split_content_by_length(content)

                #file_start = self.audio_dir+"\\"+book_name + "\\" + f"{title}-第1段.wav"
                #file_end = self.audio_dir + "\\" + book_name + "\\" + f"{title}-第{len(segments)}段.wav"

                #如果第一段和最后一段都存在则直接取值 作废
                audio_arr = []
                #if Path(file_start).exists() or Path(file_end).exists():
                #已经合成完直接去取值
                if is_synthetic_audio == '1':
                    for idx, segment in enumerate(segments):
                        file_exits = api.audio_dir+"\\"+book_name + "\\" + f"{title}-第{idx + 1}段.wav"
                        audio_arr.append(file_exits)
                    return audio_arr
                else:
                    #合成该章节，先合前5段 然后在接着合成
                    audio_arr = api.cutting_and_synthesis_audio(title, content, book_name)
                    # 返回先合成的那5个音频
                    print("同步部分合成完毕", audio_arr)
                    return audio_arr
    #映射到外面
    def api_search_next_audio_book_database(book_name,title):
        api = BiQuGeAPI()
        return api.search_next_audio_book_database(book_name,title)


if __name__ == "__main__":
    api = BiQuGeAPI()
    arr = api.mainTest('完美世界')
    print(arr)